package wiki.xsx.core.bytecode.transformer;

import javassist.bytecode.*;
import javassist.bytecode.annotation.*;
import lombok.SneakyThrows;
import wiki.xsx.core.bytecode.model.Annotations;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * 抽象注解转换器
 *
 * @author xsx
 * @date 2023/3/2
 * @since 1.8
 * <p>
 * Copyright (c) 2023 xsx All Rights Reserved.
 * easy-bytecode is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 * http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * </p>
 */
public abstract class AbstractAnnotationTransformer extends AbstractTransformer {

    /**
     * 有参构造
     *
     * @param isWrite    是否写入
     * @param fileDir    文件目录
     * @param className  类名称
     * @param methodName 方法名称
     */
    public AbstractAnnotationTransformer(Boolean isWrite, String fileDir, String className, String methodName) {
        super(isWrite, fileDir, className, methodName);
    }

    /**
     * 添加或更新注解
     *
     * @param info        操作对象
     * @param constPool   常量池
     * @param annotations 注解列表
     * @return 返回属性列表
     */
    protected List<AnnotationsAttribute> addOrUpdateAnnotation(Object info, ConstPool constPool, Annotations[] annotations) {
        List<AnnotationsAttribute> list = new ArrayList<>(2);
        AnnotationsAttribute visibleAttribute = null;
        AnnotationsAttribute invisibleAttribute = null;
        for (Annotations annotation : annotations) {
            if (annotation.getPolicy() == Annotations.Policy.RuntimeVisible) {
                if (visibleAttribute == null) {
                    visibleAttribute = this.getAttribute(info, constPool, annotation.getPolicy());
                }
                this.addOrUpdateAnnotation(visibleAttribute, constPool, annotation);
            } else {
                if (invisibleAttribute == null) {
                    invisibleAttribute = this.getAttribute(info, constPool, annotation.getPolicy());
                }
                this.addOrUpdateAnnotation(invisibleAttribute, constPool, annotation);
            }
        }
        if (visibleAttribute != null) {
            list.add(visibleAttribute);
        }
        if (invisibleAttribute != null) {
            list.add(invisibleAttribute);
        }
        return list;
    }

    /**
     * 删除注解
     *
     * @param info        操作对象
     * @param annotations 注解列表
     */
    protected void removeAnnotation(Object info, Annotations[] annotations) {
        AnnotationsAttribute visibleAttribute = null;
        AnnotationsAttribute invisibleAttribute = null;
        if (info instanceof ClassFile) {
            ClassFile classInfo = (ClassFile) info;
            visibleAttribute = (AnnotationsAttribute) classInfo.getAttribute(AnnotationsAttribute.visibleTag);
            invisibleAttribute = (AnnotationsAttribute) classInfo.getAttribute(AnnotationsAttribute.invisibleTag);
        } else if (info instanceof MethodInfo) {
            MethodInfo methodInfo = (MethodInfo) info;
            visibleAttribute = (AnnotationsAttribute) methodInfo.getAttribute(AnnotationsAttribute.visibleTag);
            invisibleAttribute = (AnnotationsAttribute) methodInfo.getAttribute(AnnotationsAttribute.invisibleTag);
        } else if (info instanceof FieldInfo) {
            FieldInfo fieldInfo = (FieldInfo) info;
            visibleAttribute = (AnnotationsAttribute) fieldInfo.getAttribute(AnnotationsAttribute.visibleTag);
            invisibleAttribute = (AnnotationsAttribute) fieldInfo.getAttribute(AnnotationsAttribute.invisibleTag);
        }
        if (visibleAttribute == null && invisibleAttribute == null) {
            return;
        }
        for (Annotations annotation : annotations) {
            this.removeAnnotation(visibleAttribute, invisibleAttribute, annotation);
        }
    }

    /**
     * 创建属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    protected MemberValue createMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Objects.requireNonNull(attribute, "the attribute must be not null");
        Objects.requireNonNull(attribute.getType(), "the attribute type must be not null");
        Objects.requireNonNull(attribute.getName(), "the attribute name must be not null");
        Objects.requireNonNull(attribute.getValue(), "the attribute value must be not null");
        switch (attribute.getType()) {
            case STRING:
                return this.createStringMemberValue(constPool, attribute);
            case BOOLEAN:
                return this.createBooleanMemberValue(constPool, attribute);
            case INTEGER:
                return this.createIntegerMemberValue(constPool, attribute);
            case ENUM:
                return this.createEnumMemberValue(constPool, attribute);
            case ANNOTATION:
                return this.createAnnotationMemberValue(constPool, attribute);
            case ARRAY:
                return this.createArrayMemberValue(constPool, attribute);
            case CLASS:
                return this.createClassMemberValue(constPool, attribute);
            case DOUBLE:
                return this.createDoubleMemberValue(constPool, attribute);
            case FLOAT:
                return this.createFloatMemberValue(constPool, attribute);
            case LONG:
                return this.createLongMemberValue(constPool, attribute);
            case SHORT:
                return this.createShortMemberValue(constPool, attribute);
            case BYTE:
                return this.createByteMemberValue(constPool, attribute);
            case CHAR:
                return this.createCharMemberValue(constPool, attribute);
            default:
                return null;
        }
    }

    /**
     * 删除注解
     *
     * @param info      操作对象
     * @param constPool 常量池
     * @param policy    注解策略
     * @return 返回属性
     */
    private AnnotationsAttribute getAttribute(Object info, ConstPool constPool, Annotations.Policy policy) {
        String tag = policy == Annotations.Policy.RuntimeVisible ? AnnotationsAttribute.visibleTag : AnnotationsAttribute.invisibleTag;
        AnnotationsAttribute attribute = null;
        if (info instanceof ClassFile) {
            ClassFile classFile = (ClassFile) info;
            attribute = (AnnotationsAttribute) classFile.getAttribute(tag);
        } else if (info instanceof MethodInfo) {
            MethodInfo methodInfo = (MethodInfo) info;
            attribute = (AnnotationsAttribute) methodInfo.getAttribute(tag);
        } else if (info instanceof FieldInfo) {
            FieldInfo fieldInfo = (FieldInfo) info;
            attribute = (AnnotationsAttribute) fieldInfo.getAttribute(tag);
        }
        return Optional.ofNullable(attribute).orElse(new AnnotationsAttribute(constPool, tag));
    }

    /**
     * 添加或更新注解
     *
     * @param attribute 属性
     * @param constPool 常量池
     * @param anno      注解列表
     */
    @SneakyThrows
    private void addOrUpdateAnnotation(AnnotationsAttribute attribute, ConstPool constPool, Annotations anno) {
        Annotation annotation = Optional.ofNullable(attribute.getAnnotation(anno.getName())).orElse(new Annotation(anno.getName(), constPool));
        Optional.ofNullable(anno.getAttributes()).ifPresent(
                attrs -> attrs.forEach(
                        attr -> annotation.addMemberValue(
                                attr.getName(), this.createMemberValue(constPool, attr)
                        )
                )
        );
        attribute.addAnnotation(annotation);
    }

    /**
     * 删除注解
     *
     * @param visibleAttribute   可见属性
     * @param invisibleAttribute 不可见属性
     * @param annotation         注解
     */
    private void removeAnnotation(AnnotationsAttribute visibleAttribute, AnnotationsAttribute invisibleAttribute, Annotations annotation) {
        if (annotation.getPolicy() == Annotations.Policy.RuntimeVisible && visibleAttribute != null) {
            visibleAttribute.removeAnnotation(annotation.getName());
        } else {
            if (invisibleAttribute != null) {
                invisibleAttribute.removeAnnotation(annotation.getName());
            }
        }
    }

    /**
     * 创建注解类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createAnnotationMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        return new AnnotationMemberValue(new Annotation(attribute.getValue().toString(), constPool), constPool);
    }

    /**
     * 创建数组类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    @SuppressWarnings("all")
    private MemberValue createArrayMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        List<Annotations.Attribute> list;
        try {
            list = (List<Annotations.Attribute>) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'List<Annotations.Attribute>'");
        }
        ArrayMemberValue memberValue = new ArrayMemberValue(constPool);
        memberValue.setValue(list.stream().map(attr -> this.createMemberValue(constPool, attr)).toArray(MemberValue[]::new));
        return memberValue;
    }

    /**
     * 创建布尔类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createBooleanMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Boolean value;
        try {
            value = (Boolean) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Boolean'");
        }
        return new BooleanMemberValue(value, constPool);
    }

    /**
     * 创建字节类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createByteMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Byte value;
        try {
            value = (Byte) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Byte'");
        }
        return new ByteMemberValue(value, constPool);
    }

    /**
     * 创建字符类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createCharMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Character value;
        try {
            value = (Character) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Character'");
        }
        return new CharMemberValue(value, constPool);
    }

    /**
     * 创建类类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createClassMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Class<?> value;
        try {
            value = (Class<?>) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Class'");
        }
        return new ClassMemberValue(value.getName(), constPool);
    }

    /**
     * 创建双精度浮点数类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createDoubleMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Double value;
        try {
            value = (Double) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Double'");
        }
        return new DoubleMemberValue(value, constPool);
    }

    /**
     * 创建枚举类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createEnumMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Enum<?> value;
        try {
            value = (Enum<?>) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Enum'");
        }
        EnumMemberValue memberValue = new EnumMemberValue(constPool);
        memberValue.setValue(value.name());
        return memberValue;
    }

    /**
     * 创建单精度浮点数类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createFloatMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Float value;
        try {
            value = (Float) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Float'");
        }
        return new FloatMemberValue(value, constPool);
    }

    /**
     * 创建整型类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createIntegerMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Integer value;
        try {
            value = (Integer) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Integer'");
        }
        return new IntegerMemberValue(value, constPool);
    }

    /**
     * 创建长整型类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createLongMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Long value;
        try {
            value = (Long) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Long'");
        }
        return new LongMemberValue(value, constPool);
    }

    /**
     * 创建短整型类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createShortMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        Short value;
        try {
            value = (Short) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'Short'");
        }
        return new ShortMemberValue(value, constPool);
    }

    /**
     * 创建字符串类型属性值
     *
     * @param constPool 常量池
     * @param attribute 属性
     * @return 返回属性值
     */
    private MemberValue createStringMemberValue(ConstPool constPool, Annotations.Attribute attribute) {
        String value;
        try {
            value = (String) attribute.getValue();
        } catch (Exception e) {
            throw new RuntimeException("the attribute must be type of 'String'");
        }
        return new StringMemberValue(value, constPool);
    }
}
